// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rbchat.logic.chat_group;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.eva.android.ArrayListObservable;
import com.eva.android.ArrayListObservable.UpdateDataToObserver;
import com.eva.android.ArrayListObservable.UpdateTypeToObserver;
import com.eva.android.ObserverMain;
import com.eva.android.ToolKits;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.widget.WidgetUtils.ToastType;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.R;
import com.x52im.rainbowchat.http.logic.dto.GroupEntity;
import com.x52im.rainbowchat.im.dto.ChatType;
import com.x52im.rainbowchat.im.dto.MsgType;
import com.x52im.rbchat.logic.alarm.AlarmType;
import com.x52im.rbchat.logic.chat_group.impl.GroupChattingMoreUIWrapperX;
import com.x52im.rbchat.cache.GroupsProvider;
import com.x52im.rbchat.logic.chat_group.impl.ProhibitSpeechChecker;
import com.x52im.rbchat.logic.chat_group.impl.ProhibitSpeechCheckerProvider;
import com.x52im.rbchat.logic.chat_group.utils.GMessageForwardHelper;
import com.x52im.rbchat.logic.chat_group.utils.GMessageHelper;
import com.x52im.rbchat.logic.chat_group.utils.GMessageReSendHelper;
import com.x52im.rbchat.logic.chat_guest.utils.TMessageForwardHelper;
import com.x52im.rbchat.logic.chat_root.AbstractChattingActivity;
import com.x52im.rbchat.logic.chat_root.AbstractChattingListAdapter;
import com.x52im.rbchat.logic.chat_root.targetchoose.TargetChooseActivity;
import com.x52im.rbchat.logic.chat_root.face.FaceBoardView;
import com.x52im.rbchat.logic.chat_root.impl.AbstractMoreUIWrapper;
import com.x52im.rbchat.logic.chat_root.impl.UnreadMessageBallonWrapper;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.sendfile.SendFileHelper;
import com.x52im.rbchat.logic.chat_root.sendimg.SendImageWrapper;
import com.x52im.rbchat.logic.chat_root.sendvoice.SendVoiceDialog;
import com.x52im.rbchat.logic.chat_root.targetchoose.TargetSource;
import com.x52im.rbchat.logic.chat_root.targetchoose.TargetSourceFilterFactory;
import com.x52im.rbchat.network.http.async.QueryGroupInfo;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadManager;
import com.x52im.rbchat.permission2.PermissionManager;
import com.x52im.rbchat.utils.ActivityRequestCode;
import com.x52im.rbchat.utils.ActivityResultCode;
import com.x52im.rbchat.utils.BroadcastToolKits;
import com.x52im.rbchat.utils.IntentFactory;
import com.x52im.rbchat.utils.PreferencesToolkits;

import java.util.ArrayList;
import java.util.Observable;

import androidx.core.app.ActivityCompat;

/**
 * 世界频道/普通群聊聊天界面.
 * <p>
 * 世界频道相当于传统的网络聊天室，在产品运营时用于提供陌生人的交流动力，
 * 不然只要加好友才能聊天，那从哪个渠道加呢？所以说，世界频道是基于产品运营
 * 而增加的功能而已，并非传统IM的必备功能哦！
 * </p>
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @author Lu
 * @since 2.4
 */
public class GroupChattingActivity extends AbstractChattingActivity {

	private final static String TAG = GroupChattingActivity.class.getSimpleName();

	//=============================================================== 标准聊天相关
//	// FIXME: 此变量因历史原因而存在，未来将逐步统一由父类中的toId替代之，本变量将在以后版本中弃用
//	/** @deprecated by super.toId  */
//	private String gidFromIntent = null;
//	// FIXME: 此变量因历史原因而存在，未来将逐步统一由父类中的toName替代之，本变量将在以后版本中弃用
//	/** @deprecated by super.toName */
//	private String gnameFromIntent = null;

	/** 全局变量：是否显示群成员昵称 */
	private boolean isShowUserNick = true;

	/** 文本组件：界面标题 */
	private TextView viewTitle;
	/** 图标组件：界面标题后面的静音图标（目前用于普通群） */
	private ImageView viewSilentIco;

	/** 按钮：发送语音留言消息 */
	private Button btnSendVoice;
	/** 按钮：发送文本消息 */
	private Button btnSend;
	/** 按钮：打开输入框下方的"更多功能"面板  */
	private Button btnOpenPlusFunctions;
	/** 按钮：返回 */
	private Button btnBack = null;
	/** 标签组件：消息输入 */
	private EditText txtMsg;
	/** 文本组件：禁止发言时的秒表提示文本 */
	private TextView viewProhibit;

	/** 父布局：聊天内容列表上方的提示信息（世界频道专用）*/
	private ViewGroup layoutUserHint = null;
	/** 父布局：是否静音（世界频道专用） */
	private ViewGroup layoutSilence = null;
	/** 按钮：是否静音（世界频道专用）  */
	private CheckBox btnSilence = null;

	/** 父布局：查看群信息（普通群专用） */
	private ViewGroup layoutViewGroupInfo = null;
	/** 按钮：查看群信息（普通群专用） */
	private Button btnViewGroupInfo = null;
	
	/** 聊天消息列表 */
	private ListView listView;
	/** 聊天消息列表adapter */
	private GroupChattingListAdapter listAdapter;
	/** 当消息列表最后一行处于可见或不可见时的新消息（未读消息）提示ui包装实现类. */
	private UnreadMessageBallonWrapper unreadMessageBallonWrapper = null;
	
	/** 聊天界面的根面板 */
	private LinearLayout layoutChattingRoot = null;
	
	/** 输入框左边功能：发送图片包装实现对象 */
	private SendImageWrapper menuWindowForSendPic = null;
	/** 输入框左边功能：发送语音留言对话框 */
	private SendVoiceDialog sendVoiceDialog = null;
	
	/** “更多“功能父布局 */
	private FrameLayout layoutbottomContent = null;
	/** "更多"功能gridview UI封装类 */
	private AbstractMoreUIWrapper bbsMoreUIWrapper = null;

	/** FFF 表情按钮及面板 */
	private Button btnOpenFace;
	private FaceBoardView faceBoardView;

	/** 列表的全局数据结构对对象 */
	private ArrayListObservable<Message> chattingDatas = null;
    /** 数据模型变动观察者实现block */
    private ObserverMain chattingDatasObserver = null;

	//=============================================================== 大文件发送相关 START
	/**
	 * 设置{@link BigFileUploadManager}中大文件任务状态改变观察者(主要用于“我”发送的大文件消息)，
	 * 用于UI及时刷新大文件上传状态在界面上的显示（本观察者通常由对应的UI界面设置，界面退到后台消失时取消设置） */
	private ObserverMain fileStatusChangedObserver = createFileStatusChangedObserver();
	//=============================================================== 大文件发送相关 END

	/**
	 * 广播监听者：用于接收群名称被修改后的的广播（用于刷新ui，及时显示）*/
	private BroadcastReceiver groupNameChangedBR = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.chatType = ChatType.CHAT_TYPE_GROUP$CHAT;
		super.onCreate(savedInstanceState);
	}

    /**
     * 解析从Intent中传过来的可能的数据本方法中实现.
     * 例如从上一个activity中传过来的数据，本方法被 {@link #init(Bundle)}调用。
     */
    @Override
    protected void initDataFromIntent() {
		super.initDataFromIntent();

        //解析从intent中传过来的数据
        ArrayList intentDatas = IntentFactory.parseBBSChatIntent(getIntent());
		super.toId = (String)intentDatas.get(0);
		super.toName = (String)intentDatas.get(1);
    }
	
	/**
	 * 根据Activity的生命周期：本Activity每次从后台回到前台时调用本方法。
	 */
	protected void onResume() {
		super.onResume();

		// 及时根据群信息里设置的"显示群成员昵称"，刷新ui显示(当新老值不一致时即表示标识已被更新——是时候更新ui了)
		boolean isShowUserNick_old = this.isShowUserNick;
		this.isShowUserNick = PreferencesToolkits.isGroupShowNick(this, toId);
		if(isShowUserNick_old != this.isShowUserNick)
			listAdapter.notifyDataSetChanged();

		// 及时根据群信息里设置的"消息免打扰"，刷新ui显示
		this.viewSilentIco.setVisibility(!PreferencesToolkits.isChatMsgToneOpen(this, toId)?View.VISIBLE:View.GONE);
		
		// 设置当前正处于激话状态下的聊时聊天群组id
		imc().setCurrentFrontGroupChattingGroupID(toId);

		// 设置大文件上传状态变更观察者
		imc().getBigFileUploadManager().setFileStatusChangedObserver(fileStatusChangedObserver);

		// 世界频道
		if(isWorldChat()) {
			// APP中唯一重置未读BBS聊天消息的代码：最后确保重置APP首页“BBS消息”未读消息数字的显示
			imc().getAlarmsProvider().getBBSAlarmData().resetFlagNum(true);
		} else {
			// APP中唯一重置未读普通群聊消息的代码：最后确保重置APP首页“消息”未读消息数字的显示
			imc().getAlarmsProvider().resetFlagNum(AlarmType.groupChat, toId, 0, true);
			// 及时刷新标题显示（因为从标题修改界面返回、或者群组标题被群主修改的情况下，标题需要及时更新）
			GroupEntity ge = imc().getGroupsProvider().getGroupInfoByGid(toId);
			if(ge != null) {
                updateTitle(ge.getG_name());
            }
		}
	}
	
	/**
	 * 根据Activity的生命周期：本Activity每次从前台退到后台时调用本方法。
	 */
	protected void onPause() {
		super.onPause();

		// 取消设置当前正处于激话状态下的群组聊天id
		imc().setCurrentFrontGroupChattingGroupID(null);

		if(!isWorldChat())
			// 取消设置大文件上传状态变更观察者
			imc().getBigFileUploadManager().setFileStatusChangedObserver(null);
		
		// 如果正在进行语音留言录制的话就hide它
		if(sendVoiceDialog != null)
			sendVoiceDialog.hide();
	}
	
	/**
	 * 本activity被销毁时额外要做一些事.
	 */
	@Override
	protected void onDestroy() {
		// 如果正在进行语音留言录制的话就销毁它
		if(sendVoiceDialog != null) {
			sendVoiceDialog.dismiss();
		}
		
		// 释放ListAdapter中需要释放的资源
		if(listAdapter != null) {
			listAdapter.forParentDestraoy();
		}

        // 逆初始化：释放资源
        deInitToGroup(toId);
		
		// 取消设置禁言观察者
        ProhibitSpeechCheckerProvider.getInstance().getChecker(toId).setProhibitObserver(null);

		// 取消注册广播监听者：群名称被修改后的广播
		BroadcastToolKits.groupNameChanged_UNREGISTER(this, groupNameChangedBR);

		super.onDestroy();
	}

	/**
	 * 本方法由父类的onCreate()调用，子类可在此方法中实现自已的UI显示逻辑。
	 */
	@Override
	protected void initViews(Bundle savedInstanceState) {
		super.initViews(savedInstanceState);

		// 养成良好习惯：首先设置主layout，确保后绪的操作中使用到的组件都可以被find到
		setContentView(R.layout.groupchat_chatting_list_view);

		// 各功能组件
		viewTitle = (TextView) this.findViewById(R.id.multi_chatting_list_view_titileView);
		viewSilentIco = (ImageView) this.findViewById(R.id.multi_chatting_list_view_silentIconView);
		txtMsg = (EditText) this.findViewById(R.id.multi_chatting_list_view_msgEdit);
		btnSendVoice = (Button) this.findViewById(R.id.multi_chatting_list_view_sendVoiceBtn);
		btnSend = (Button) this.findViewById(R.id.multi_chatting_list_view_sendBtn);
		btnOpenPlusFunctions = (Button) this.findViewById(R.id.multi_chatting_list_view_plusBtn);
		btnBack = (Button) this.findViewById(R.id.multi_chatting_list_view_backBtn);
		viewProhibit = (TextView) this.findViewById(R.id.multi_chatting_list_view_prohibitText);
		layoutUserHint = (ViewGroup) this.findViewById(R.id.multi_chatting_list_view_userHintForBBS);
		layoutSilence = (ViewGroup) this.findViewById(R.id.multi_chatting_list_view_silenceLayout);
		btnSilence = (CheckBox) this.findViewById(R.id.multi_chatting_list_view_silenceBtn);
		layoutViewGroupInfo = (ViewGroup) this.findViewById(R.id.widget_title_viewGroupInfoLayout);
		btnViewGroupInfo = (Button) this.findViewById(R.id.widget_title_right_viewGroupInfoBtn);

		// 设置右上角的按钮可见性等
		if(isWorldChat()) {
			layoutSilence.setVisibility(View.VISIBLE);
			layoutViewGroupInfo.setVisibility(View.GONE);
			layoutUserHint.setVisibility(View.VISIBLE);
		}
		else {
			layoutSilence.setVisibility(View.GONE);
			layoutViewGroupInfo.setVisibility(View.VISIBLE);
			layoutUserHint.setVisibility(View.GONE);

			// 显示界面标题
            updateTitle(toName);
		}

		// 消息列表
		listView = (ListView) findViewById(R.id.multi_chatting_list_view_listView);
		// 初始化消息列表及列表的adapter
		initListViewAndAdapter();
		
		// 聊天界面的根面板
		layoutChattingRoot = (LinearLayout)this.findViewById(R.id.multi_chatting_list_view_rootLL);
		// 输入框下方的功能区父布局
		layoutbottomContent = (FrameLayout)this.findViewById(R.id.multi_chatting_list_view_bottomContentFL);
				
		// 实例化发送图片包装实现类
		menuWindowForSendPic = new SendImageWrapper(this, layoutChattingRoot, toId, Const.CHAT_TYPE_GROUP$CHAT);

		// 输入框下方的更多功能
		this.initChatFunctionsUI();
		// FFF 表情按钮及面板
		this.initFaceUI();
		// 初始化聊天数据
		this.initToGroup();
		// 设置禁言观察者
		this.initProhibitSpeechChecker();
	}

	/**
	 * 本方法由父类的onCreate()调用，子类可在此方法中为各UI功能组件增加事件临听。
	 */
	protected void initListeners() {
		//** 消息输入框左边的语音留言按钮事件处理
		btnSendVoice.setOnClickListener(v -> {
			if(!ProhibitSpeechCheckerProvider.getInstance().getChecker(toId).isProhibit()) {
				// 开始动态权限申请（用于targetSDKVersion>=23，即Android6.0及以上系统时）
				PermissionManager.requestPermission_RECORD_AUDIO(GroupChattingActivity.this
						// 权限获取成功或已经取得此权限时
						, (o, arg) -> {
							//** 权限正常获取，走正常的代码逻辑。。。。

							// 消息列表首先自动滚动到最后一行
							listAdapter.showLastItem();
//							System.out.println("【禁言GUI】当前未禁言中，消息正式发出。。。");
							// 把句柄缓存起来，以便 下次打开时能快一点（经实测，跟每次都new SendVoiceDialog，确实要快很多哦！）
							if(sendVoiceDialog == null)
								sendVoiceDialog = new SendVoiceDialog(GroupChattingActivity.this, toId, Const.CHAT_TYPE_GROUP$CHAT);
							// 显示之
							sendVoiceDialog.show();
						}
						, null);
			}
			else {
				hintForProhibit();
//				System.out.println("【禁言GUI】当前禁言中，消息无法发出去！");
			}
		});
		
		//**　文本消息发送按钮事件处理
		btnSend.setOnClickListener(v -> {
			if(!ProhibitSpeechCheckerProvider.getInstance().getChecker(toId).isProhibit()) {
//				System.out.println("【禁言GUI】当前未禁言中，消息正式发出。。。");
				sendPlainTextMessage(new ObserverMain(){
					@Override
					public void updateInMain(Observable observable, Object data) {
						txtMsg.setText("");
//						System.out.println("【禁言GUI】消息已成功发出。");
						// 更新刷屏禁言计时显示
						ProhibitSpeechCheckerProvider.getInstance().getChecker(toId).updateForSend();
					}
				});
			}
			else {
				hintForProhibit();
//				System.out.println("【禁言GUI】当前禁言中，消息无法发出去！");
			}
		});

		//** 为灌水禁止发言提示时间文本添加点击事件
		// 当本组件显示时，表法已经处于灌水计数检查周期内，用户点击就提示当前处于灌水解禁计时中
		viewProhibit.setOnClickListener(v -> hintForProhibit());

		//** 标题栏的返回按钮事件处理
		btnBack.setOnClickListener(v -> GroupChattingActivity.this.finish());

		// "世界频道"才显示静音按钮
		if (isWorldChat()) {
			//** 世界频道静音按钮事件处理
			btnSilence.setOnCheckedChangeListener((buttonView, isChecked) -> {
				if (isChecked) {
					PreferencesToolkits.setChatMsgToneOpen(self(), false, GroupEntity.DEFAULT_GROUP_ID_FOR_BBS);
					btnSilence.setBackgroundResource(R.drawable.multi_chatting_list_view_silence_on);
					btnSilence.setGravity(Gravity.RIGHT | Gravity.CENTER);

					WidgetUtils.showToast(self(), "世界频道静音模式已开启！", ToastType.OK);
				} else {
					PreferencesToolkits.setChatMsgToneOpen(self(), true, GroupEntity.DEFAULT_GROUP_ID_FOR_BBS);
					btnSilence.setBackgroundResource(R.drawable.multi_chatting_list_view_silence_off);
					btnSilence.setGravity(Gravity.LEFT | Gravity.CENTER);
					WidgetUtils.showToast(self(), "世界频道静音模式已关闭！", ToastType.OK);
				}
			});
			// 世界频道静音按钮UI状态设置
			btnSilence.setChecked(!PreferencesToolkits.isChatMsgToneOpen(this, GroupEntity.DEFAULT_GROUP_ID_FOR_BBS));
		}
		// 普通群就显示查看群信息按钮
		else {
			// 查看群基本信息
			btnViewGroupInfo.setOnClickListener(v -> QueryGroupInfo.gotoWatchGroupInfo(self(), toId, null));
		}

		//** 初始化输入框下方的”更多“功能按钮、面板等事件处理逻辑
		initChatFunctionsLisnter();

		//** 长按弹出菜单事件注册和处理逻辑
		super.initListViewListenersImpl(this, listView, listAdapter);
	}

	/**
	 * 初始化各广播监听器。
	 */
	@Override
	protected void initBroadCastReciever() {
		super.initBroadCastReciever();

		groupNameChangedBR = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				String gid = intent.getStringExtra("gid");
				String newGroupName = intent.getStringExtra("newGroupName");
				Log.i(TAG, "【群名称被修改】收到 (gid="+gid+"，newGroupName="+newGroupName+") 已被修改的广播通知！");

				if(gid != null && gid.equals(toId)) {
					// 更新聊天界面上的标题栏
					updateTitle(newGroupName);
					Log.i(TAG, "【好友备注更新】当前聊天界面标题更新成功！");
				}
			}
		};
		// 注册广播监听者：用于接收修改完成好友的备注后的广播通知
		BroadcastToolKits.groupNameChanged_REGISTER(this, groupNameChangedBR);
	}
	
	/**
	 * 初始化消息列表及列表的adapter。
	 */
	private void initListViewAndAdapter() {
		// 生成适配器的ImageItem <====> 动态数组的元素，两者一一对应
		if(chattingDatas == null)//先实例化列表的全局数据结构对对象（如果已经实例化了就不需要现新那对 象了，目的是为了保留上次的数据）
			chattingDatas = new ArrayListObservable<>();
		
		// 初始化当消息列表最后一行处于可见或不可见时的新消息（未读消息）提示ui包装实现对象
		unreadMessageBallonWrapper = new UnreadMessageBallonWrapper(this, R.id.multi_chatting_list_view_unreadBallonBtn){
			/** 
			 * 当点击未读气泡时要调用的方法.
			 */
			@Override
			protected void fireOnClickBallon() {
				// 点击未读气泡时自动让消息列表滚动到最后一行
				if(listAdapter != null)
					listAdapter.showLastItem();
			}
		};
		
		listAdapter = new GroupChattingListAdapter(this, listView);
		listView.setAdapter(listAdapter);
	}

	private void initFaceUI() {
		btnOpenFace = this.findViewById(R.id.multi_chatting_list_view_openFaceBtn);
		faceBoardView = this.findViewById(R.id.i_faceboard);
		faceBoardView.setInputEt(txtMsg); // 绑定输入框

		// 表情面板的事件处理（出于通用目的，暂时只有OnOperationListener事件被提炼到父类中）
		super.initFaceListenersImpl(this, faceBoardView, txtMsg);

		// 表情面板显示后要做的事：消息列表滚动到底部
		faceBoardView.setOnToolBoxListener(new FaceBoardView.OnToolBoxListener() {
			@Override
			public void onShowFace() {
//				ToolKits.hideInputMethod(ChatActivity.this, txtMsg);
				bbsMoreUIWrapper.hide();
				listAdapter.showLastItem();
				// 按钮变为键盘模式
				btnOpenFace.setBackgroundResource(R.drawable.bbs_chatting_keyboard_btn);
			}
			@Override
			public void onHideFace() {
				listAdapter.showLastItem();
				// 按钮变为表情模式
				btnOpenFace.setBackgroundResource(R.drawable.bbs_chatting_face_btn);
			}
		});

		// FFF 打开表情面板
		btnOpenFace.setOnClickListener(faceBoardView.getFunctionBtnListener(1));
	}
	
	/**
	 * 初始化输入框下方的”更多“功能面板的UI
	 */
	private void initChatFunctionsUI() {
		// 实例化输入框下方的更多功能封装实现类
		this.bbsMoreUIWrapper = new GroupChattingMoreUIWrapperX(this, this.layoutbottomContent) {
			/**
			 * 点击了gridview中的相应功能时将自动触发本方法。
			 * 
			 * @param actionId 相应功能的action id
			 */
			@Override
			protected void fireChatFunctionsAction(int actionId) {
				switch(actionId) {
					case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_IMAGE:
						menuWindowForSendPic.doChoosePhoto();
						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
					case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_PHOTO:
						menuWindowForSendPic.doTakePhoto();
						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
					case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_FILE:
						SendFileHelper.openFileChooser(GroupChattingActivity.this);
						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
                    case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_SHORTVIDEO: {
						// 开始动态权限申请（用于targetSDKVersion>=23，即Android6.0及以上系统时）
						PermissionManager.requestPermission_CAMERA_$_RECORD_AUDIO(GroupChattingActivity.this
								// 权限获取成功或已经取得此权限时，走正常的代码逻辑，进入短视频录制界面
								, (o, arg) -> ActivityCompat.startActivityForResult(self(),IntentFactory.createShortVideoRecordActivityIntent(self()), ActivityRequestCode.SHORTVIDEO_RECORD_RESULT_FROM_ACTIVITY, null)
								, null);

						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
					}
					case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_CARD_FRIEND: {
//						Intent i = IntentFactory.createUserChooseActivityIntent(self()
//								, TargetChooseActivity.USED_FOR_SEND_CONTACT_MESSAGE, Const.CHAT_TYPE_GROUP$CHAT, toId);
						Intent i = IntentFactory.createTargetChooseActivityIntent(self()
								, TargetSource.friend, null, TargetSourceFilterFactory.createTargetSourceFilter4UserContact(chatType, toId), null, null);
						ActivityCompat.startActivityForResult(self(), i, ActivityRequestCode.REQUEST_CODE_FOR_CONTACT_TARGET_CHOOSE, null);
						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
					}
					case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_CARD_GROUP: {
						Intent i = IntentFactory.createTargetChooseActivityIntent(self()
								, TargetSource.group, null, null,  TargetSourceFilterFactory.createTargetSourceFilter4GroupContact(chatType, toId), null);
						ActivityCompat.startActivityForResult(self(), i, ActivityRequestCode.REQUEST_CODE_FOR_CONTACT_TARGET_CHOOSE, null);

						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
					case GroupChattingMoreUIWrapperX.MORE_ACTION_ID_LOCATION: {
						Intent intent = IntentFactory.createGetLocationActivityIntent(self());
						ActivityCompat.startActivityForResult(self(), intent, ActivityRequestCode.REQUEST_CODE_FOR_GETTION, null);
						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
					}
				}
			}

			@Override
			protected boolean isForBBS() {
				return isWorldChat();
			}
		};
	}
	
	/**
	 * 初始化输入框下方的”更多“功能按钮、面板等事件处理逻辑.
	 */
	private void initChatFunctionsLisnter() {
		//* "+"号按钮事件处理
		btnOpenPlusFunctions.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// 尝试关闭输入法（如果输入法已开启，无条件关闭之）
				ToolKits.hideInputMethod(self(), txtMsg);
				// 尝试关闭表情面板
				faceBoardView.hideLayout();

				// "+"号对应的内容面板当前未显示时（接下来应该被显示）
				if(!bbsMoreUIWrapper.isShowing()) {
					// 延迟一会，让键盘先隐藏再显示内容面板，否则会有一瞬间内容面板和软键盘同时显示的ui跳动
					layoutbottomContent.postDelayed(() -> {
						switchBottomContent();
					}, 50);
				} else {
					switchBottomContent();
				}
			}

			private void switchBottomContent() {
				// 打开或关闭更多功能面板
				bbsMoreUIWrapper.auto();
				// 消息列表首先自动滚动到最后一行
				listAdapter.showLastItem();
			}
		});

		//* 点击消息输入文本框时强制隐藏”更多“功能面板（如果它正在显示的话）
		txtMsg.setOnClickListener(v -> {
			// 消息列表首先自动滚动到最后一行
			listAdapter.showLastItem();
			// 关闭表情面板
			faceBoardView.hideLayout();
			// 当触发输入框时无条件关闭“更多”面板
			bbsMoreUIWrapper.hide();
		});
		
		//* 消息输入文本框获得焦点时隐藏”更多“功能面板（如果它正在显示的话）
		txtMsg.setOnFocusChangeListener((v, hasFocus) -> {
			if (hasFocus) {
				// 关闭表情面板
				faceBoardView.hideLayout();
				// 当输入框获点输入焦点时无条件关闭“更多”面板
				bbsMoreUIWrapper.hide();
			}
		});
		
		//* 消息输入文本框中有内容或无内容时隐藏或显示”更多“功能按钮（如果它正在显示的话）
		txtMsg.addTextChangedListener(new TextWatcher() {
			@Override
			public void onTextChanged(CharSequence s, int start, int before, int count) {
				autoSwitchSendAndPlusBtn();
			}

			@Override
			public void beforeTextChanged(CharSequence s, int start, int count, int after) {
				autoSwitchSendAndPlusBtn();
			}

			@Override
			public void afterTextChanged(Editable s) {
				autoSwitchSendAndPlusBtn();
			}

			// 如果消息文本框中有文本时设置消息”发送“按钮可见、”更多“功能按钮不可见，否则相反
			private void autoSwitchSendAndPlusBtn() {
				String txt = txtMsg.getText().toString();
				if (txt != null && txt.length() > 0) {
					btnSend.setVisibility(View.VISIBLE);
					btnOpenPlusFunctions.setVisibility(View.GONE);
				} else {
					btnSend.setVisibility(View.GONE);
					btnOpenPlusFunctions.setVisibility(View.VISIBLE);
				}
			}
		});  
		
		//*聊天列表listview的触摸事件(当上下滑动消息列表时)：
		// 1) 无条件关闭“更多”面板，就像微信一样；
		// 2) 如果输入法已开启，无条件关闭之，就像微信一样。
		listView.setOnTouchListener((v, event) -> {
			switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
				case MotionEvent.ACTION_UP:
				case MotionEvent.ACTION_MOVE: {
					// 如果输入法已开启，无条件关闭之，就像微信一样
					ToolKits.hideInputMethod(GroupChattingActivity.this, txtMsg);
					// 当上下滑动消息列表时无条件关闭“更多”面板，就像微信一样
					bbsMoreUIWrapper.hide();
					// 关闭表情面板
					faceBoardView.hideLayout();
					break;
				}
			}
			return false;
		});
	}
	
	/**
	 * 初始化灌水检查机制。
	 */
	private void initProhibitSpeechChecker() {
		ProhibitSpeechCheckerProvider.getInstance().getChecker(toId).setProhibitObserver((observable, data) -> {
			int[] values = (int[]) data;
			if (values != null && values.length == 2) {
				int type = values[0];
				// 发言被禁通知：prohibitNow
				if (type == ProhibitSpeechChecker.OBSERVER_NOTIFY_TYPE_PROHIBITNOW) {
					viewProhibit.setVisibility(View.VISIBLE);
					// 显示自动解禁初始值
					viewProhibit.setText(String.valueOf(ProhibitSpeechChecker.AUTO_PROHIBIT_COUNTER_COUNT));
				}
				// 发言解禁通知：unprohibitNow
				else if (type == ProhibitSpeechChecker.OBSERVER_NOTIFY_TYPE_UNPROHIBITNOW) {
					viewProhibit.setVisibility(View.GONE);
					WidgetUtils.showToast(GroupChattingActivity.this, "发言限制已经解除，请勿继续刷屏哦！", ToastType.OK);
				}
				// 解禁剩余时间通知：fireSecondChanced
				else if (type == ProhibitSpeechChecker.OBSERVER_NOTIFY_TYPE_PROHOBITREMAIN) {
					if (viewProhibit.getVisibility() != View.VISIBLE)
						viewProhibit.setVisibility(View.VISIBLE);
					viewProhibit.setText(String.valueOf(values[1]));
				}
			}
		});
	}
	
	/**
	 * 初始化与该群的聊天相关设置.
	 */
	private void initToGroup() {
		// 当用户收到新消息时是通过通知观察者来刷新聊天界面中的ui显示的（从而保证显示最新消息）
		chattingDatasObserver = createChattingDatasObserver();
		// 切换到指定群的聊天记录
		chattingDatas = imc().getGroupsMessagesProvider().getMessages(this, toId);
		// 设置数据更新观察者
        chattingDatas.addObserver(chattingDatasObserver);

		// 设置聊天列表的数据模型对象
		listAdapter.setListData(chattingDatas.getDataList());
		// 刷新列表ui显示
		listAdapter.notifyDataSetChanged();

		// true表示高亮成功（高亮当前用于从搜索功能中进入到聊天界面中，从而让搜索到的消息高亮显示并自将将该条滚动到列表可视区）
		boolean highlightOnceMessage = doHighlightOnceMessage(listView);
		// 当高亮不成功时，就自动滚到最后一行
		if(!highlightOnceMessage) {
			// 自动滚动到最后一行
			listAdapter.showLastItem();
		}
	}

    /**
     * 逆初始化与该群的聊天相关设置.
     * <p>
     * 本方法在资源回收时调用，是方法 {@link #initToGroup()}的逆方法.
     *
     * @param gidForInit
     */
    private void deInitToGroup(String gidForInit) {
        // 切换到该用户的聊天记录
        chattingDatas = imc().getGroupsMessagesProvider().getMessages(this, gidForInit);
		// 取消数据观察者
        if (chattingDatas != null && chattingDatasObserver != null) {
            chattingDatas.removeObserver(chattingDatasObserver);
        }
    }

	/**
	 * 刷新标题栏的显示。
	 *
	 * @param gname
	 */
	private void updateTitle(String gname) {
		toName = gname;
		// 显示昵称
		this.viewTitle.setText(toName);
	}

	/**
	 * 创建收到新消息的观察者。
	 * <p>
	 * 本观察者的作用是：用于当收到用户消息时，能由消息监督听者通知此观察者来完成ui的刷新（这样就能保证最新消息能即时显示在界面上了）。
	 *
	 * @return 观察者对象
	 */
	private ObserverMain createChattingDatasObserver(){
		return new ObserverMain() {
					@Override
					public void updateInMain(Observable observable, Object data) {
						listAdapter.notifyDataSetChanged();

						if (data != null) {
							UpdateDataToObserver<Message> udto = (UpdateDataToObserver<Message>) data;

							// 添加到聊天界面中的是"我"自已发出的消息
							if (udto.getExtraData() != null && udto.getExtraData().isOutgoing()) {
								// 自动让消息列表滚动到最后一行（不然会挡住自已新发的消息，影响体验哦）
								if (listAdapter != null)
									listAdapter.showLastItem();
							} else {
								// 【消息列表最后一条不可见时的处理1】当消息列表的最后不处于可见状态时（通常此时是由用户滑道列表上的面看历史消息了）
								if (!listAdapter.isLastItemVisible()) {
									if (udto != null && udto.getUpdateType() == UpdateTypeToObserver.add) {
										// 新消息作为未读消息提示用户（有几条新消息未读——即有几条未读的正处于末尾的不可见状态）
										if (unreadMessageBallonWrapper != null)
											unreadMessageBallonWrapper.addUnreadCount(1);
									}
								}
								// 最后一条可见时，则无条件保证自动滚动到最后一行
								else {
									if (listAdapter != null)
										listAdapter.showLastItem();
								}
							}
						}
					}
				};
	}

	/**
	 * 大文件上传任务状态改变观察者.
	 *
	 * @return
	 */
	private ObserverMain createFileStatusChangedObserver() {
		return new ObserverMain(){
			@Override
			protected void updateInMain(Observable o, Object data) {
				// 收到状态改变通知就直接刷新消息列表UI
				listAdapter.notifyDataSetChanged();
			}
		};
	}
	
	/**
	 * 默认将聊天文本框中的内容作为发送内分给好友.
	 */
	protected void sendPlainTextMessage(ObserverMain sucessObs) {
		sendPlainTextMessage(txtMsg.getText().toString(), sucessObs);
	}

	public void sendPlainTextMessage(String message, ObserverMain sucessObs) {
		GMessageHelper.sendPlainTextMessageAsync(this, toId, message, sucessObs);
	}
	
	/**
	 * 处理回调.
	 * {@inheritDoc}
	 */
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		// 从相册或相机拍照完成后的回调
		if (requestCode == ActivityRequestCode.TAKE_BIG_PICTURE || requestCode == ActivityRequestCode.CHOOSE_BIG_PICTURE2) {
			menuWindowForSendPic.onParantActivityResult(requestCode, resultCode, data);
		}
		// 选中要发送的大文件后的回调
		else if (requestCode == ActivityRequestCode.CHOOSE_FILE_RESULT_FROM_ACTIVITY) {
			SendFileHelper.onParantActivityResult(this, requestCode, resultCode, data, Const.CHAT_TYPE_GROUP$CHAT, toId, null);
		}
		// 从群信息界面传回来的回调情况
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_VIEW_GROUP_INFO) {
			// 从退群或解散群回来时，直接把本聊天界面关闭（难道还能聊？）
			if (resultCode == ActivityResultCode.RESULT_CODE_FOR_DISMISS_GROUP || resultCode == ActivityResultCode.RESULT_CODE_FOR_QUIT_GROUP) {
				finish();
			}
		}
		// 短视频录制完成后的回调
		else if (requestCode == ActivityRequestCode.SHORTVIDEO_RECORD_RESULT_FROM_ACTIVITY) {
			super.onActivityResult_shortVideoRecordComplete(data);
		}
		// "名片"消息时，选择要发送的用户或群的回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_CONTACT_TARGET_CHOOSE) {
			super.onActivityResult_contactTargetSelectedComplete(this, listAdapter, data);
		}
		// 消息转发时，选择转发接收者（用户或群）后的回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_FORWARD_TARGET_CHOOSE) {
			super.onActivityResult_forwardTargetSelectedComplete(this, listAdapter, data);
		}
		// "位置"消息时，选择要发送的位置的回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_GETTION) {
			super.onActivityResult_locationSelectedComplete(this, data);
		} else {
			;// do nothing
		}
	}

	/**
	 * 重写父类方法，加上群主权限的判断。
	 *
	 * {@inheritDoc}
	 *
	 * @param d 聊天消息数据对象
	 * @return true表示该消息可被撤回
	 * @since 7.3
	 */
	@Override
	protected boolean messageCanBeRevoke(Message d) {
		if (d != null) {
			// 除系统消息、已被撤回的消息不能被撤回
			if (d.getMsgType() != MsgType.TYPE_SYSTEAM$INFO && d.getMsgType() != MsgType.TYPE_REVOKE) {
				// 管理员有权限撤回别人的消息
				boolean isGroupOwner = GroupsProvider.isThisGroupOwner(toId);
				// 群管理员可无条件、无时限地撤回所有消息（包括自已发出的、别人发出的）
				if(isGroupOwner && ((d.isOutgoing() && d.getSendStatus() == Message.SendStatus.beReceived) || !d.isOutgoing()))
					return true;
				// 非管理员则只能撤回自已发出的 且 在撤回时限内的消息
				else {
					if (d.isOutgoing() && d.getSendStatus() == Message.SendStatus.beReceived) {
						return messageIsNotTimeoutForRevoke(d);
					}
				}
			}
		}

		return false;
	}

	/**
	 * 刷新聊天列表。
	 */
	@Override
	protected void  notifyListDataSetChanged() {
		this.listAdapter.notifyDataSetChanged();
	}

//	/** 刷新界面标题的显示 */
//	private void refreshTitle() {
//        if(toId != null) {
//            // 界面标题
//            viewTitle.setText(toName);
//        }
//    }

    private void hintForProhibit() {
        WidgetUtils.showToast(self(), $$(R.string.bbs_send_message_prohibit_hint), ToastType.INFO);
    }


    //----------------------------------------------------------------- 以下为内部类

	/**
	 * 自定义群聊消息列表的adapter类实现.
	 */
	private class GroupChattingListAdapter extends AbstractChattingListAdapter {
		public GroupChattingListAdapter(Activity context, ListView theListViewBtContact) {
			super(context, theListViewBtContact, null, false);
		}
		
		// 重写父类方法的目的是为了解决BBS数据模型自动裁剪后导致的数据数组越界问题（
		// 因为Ui的刷新和数据的填充可能不是在一个线程里完成，数据被裁剪后用于刷新ui的
		// position不一定是合法的（可能超出））
		@Override 
		public int getItemViewType(int position) {
			// FIX BUG: 20140313_1
			/* Invalid index 52, size is 52
				java.lang.IndexOutOfBoundsException: Invalid index 52, size is 52
				at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
				at java.util.ArrayList.get(ArrayList.java:308)
				at com.eva.android.ArrayListObservable.get(ArrayListObservable.java:62)
				at ...logic.chat.AbstractChattingListAdapter.getItemViewType(AbstractChattingListAdapter.java:91)
			 */
			// 此判断用于解决BBS数据模型自动裁剪后导致的数据数组越界问题
			if(positionValid(position))
				return super.getItemViewType(position);
			
			return -1; 
		}
		
		// 重写父类方法的目的是为了解决BBS数据模型自动裁剪后导致的数据数组越界问题（因为Ui的刷新和数据的填充可能
		// 不是在一个线程里完成，数据被裁剪后用于刷新ui的position不一定是合法的（可能超出））
		@Override
		public View getView(final int position, View convertView, ViewGroup parent) {
			// FIX BUG: 20140313_1
			// 此判断用于解决BBS数据模型自动裁剪后导致的数据数组越界问题
			if(positionValid(position))
				return super.getView(position, convertView, parent);
			return null;
		}
		
		private boolean positionValid(int position) {
			return getChattingDatas() != null && (position >=0 && position <= getChattingDatas().getDataList().size() - 1);
		}
		
		/**
		 * 图片消息的图片上传到服务端（用于暂存和中转）成功后即发送一条消息给聊天的对方.
		 * 
		 * @param msgType MsgType
		 * @param imageOrVoiceFileName
		 * @param fingerPring
		 */
		@Override
		protected void sendImageOrVoiceMessageAsyncImpl(int msgType, boolean isOutgoing, String imageOrVoiceFileName, String fingerPring) {
			if (isOutgoing) {
				if (msgType == MsgType.TYPE_IMAGE)
					// 并发送一条图片消息给好友（之前图片已成功上传到服务端罗！）
					GMessageHelper.sendImageMessageAsync((Activity) context, toId, imageOrVoiceFileName, fingerPring, null);
				else if (msgType == MsgType.TYPE_VOICE)
					// 并发送一条图片消息给好友（之前图片已成功上传到服务端罗！）
					GMessageHelper.sendVoiceMessageAsync((Activity) context, toId, imageOrVoiceFileName, fingerPring, null);
			}
		}
		
		@Override
		protected ArrayListObservable<Message> getChattingDatas() {
			return chattingDatas;
		}
		
		/**
		 * 当列表滑到最后一行时将调用的方法.
		 */
		@Override
		protected void onScrollToBottom() {
			if(unreadMessageBallonWrapper != null)
				// 【当消息列表真滑动到最后一条可见时的处理3】设置“未读数”提示UI不可见（重置未读数为0）
				unreadMessageBallonWrapper.resetUnreadCount();
		}

        /**
         * 消息重发实现方法。
         * 子类需实现本方法，从而实现消息的重发逻辑。
         *
         * @param entity
         */
        @Override
        protected void reSendImpl(Message entity) {
			if(entity.isForwardOutgoing()) {
				GMessageForwardHelper.forward((Activity) context, entity, toId, null);
			} else {
				GMessageReSendHelper.reSend((Activity) context, entity, toId);
			}
        }

		/**
		 * 聊天界面中是否显示用户昵称。父类默认不显示。
		 * 子类可重写本方法，返回true即可显示昵称。
		 *
		 * @return true表示显示昵称，否则不显示
		 */
		@Override
		protected  boolean isShowNickName() {
			return isShowUserNick;
		}
	}
}
